﻿using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.Linq;
using ChiantiPhoneListApp.Threading;
using System.Threading;

namespace ChiantiPhoneListApp
{
    public delegate void DealDataDownloadComplete();
 
    public class MainViewModel : INotifyPropertyChanged
    {
        private AutoResetEvent monitor;
        private Semaphore loadSemaphore = null;
        private OnSelectedDealChanged _onSelectedDealChanged;
        private OnSelectedDealExpired _onSelectedDealExpired;
        private OnDealExpired _onDealExpired;

        public OnDealExpired OnDealExpired
        {
            get { return _onDealExpired; }
            set { _onDealExpired = value; }
        }


        public OnSelectedDealExpired OnSelectedDealExpired
        {
            get { return _onSelectedDealExpired; }
            set { _onSelectedDealExpired = value; }
        }
        private DealDataDownloadComplete _onDealDataDownloadComplete; 

        public OnSelectedDealChanged OnSelectedDealChanged
        {
            get { return _onSelectedDealChanged; }
            set { _onSelectedDealChanged = value; }
        }
        /// <summary>
        /// A collection for ItemViewModel objects.
        /// </summary>
        public ObservableCollection<ChiantiServiceReference5.Deal> Deals { get; private set; }
        public ObservableCollection<ChiantiServiceReference5.Business> Businesses { get; private set; }
        public ObservableCollection<ChiantiServiceReference5.DealBid> DealBids { get; private set; }
        public ObservableCollection<ChiantiServiceReference5.DealSummary> DealSummaries { get; set; }

        public ChiantiServiceReference5.Deal SelectedDeal { get; private set; }
        public ChiantiServiceReference5.Business SelectedBusiness { get; private set; }
        public ChiantiServiceReference5.DealBid SelectedActiveDealBid { get; private set; }


        private Refresh onViewModelUpdate = null;
        private ChiantiServiceReference5.DealManagementServiceClient _client = null;
        private bool refreshAllDataOnUpdate = false;


        private bool isDealLoading = false;
        private bool isBusinessLoading = false;
        private bool isDealBidsLoading = false;

        public MainViewModel()
        {
            this.Deals = new ObservableCollection<ChiantiServiceReference5.Deal>();
            this.Businesses = new ObservableCollection<ChiantiServiceReference5.Business>();
            this.DealBids = new ObservableCollection<ChiantiServiceReference5.DealBid>();
            this.DealSummaries = new ObservableCollection<ChiantiServiceReference5.DealSummary>();
            this.loadSemaphore = new Semaphore(1);
            this._onDealDataDownloadComplete += OnDealDataDownloadComplete;
            this.monitor = new AutoResetEvent(false);
        }

        public  Refresh OnViewModelUpdate
        {
            get { return this.onViewModelUpdate; }
            set { this.onViewModelUpdate = value; }
        }

        private void OnDealDataDownloadComplete()
        {
            while (isDealBidsLoading || isBusinessLoading || isDealLoading)
            {
                monitor.WaitOne();
            }
            
            this.isDealLoading.ToString();
            this.isBusinessLoading.ToString();
            this.isDealBidsLoading.ToString();

           

            Deployment.Current.Dispatcher.BeginInvoke(() =>
           {
               DealSummaries.Clear();

               foreach (ChiantiServiceReference5.Deal deal in Deals)
               {
                   ChiantiServiceReference5.Business business = GetBusiness(deal.BusinessID);
                   DealSummaries.Add(new ChiantiServiceReference5.DealSummary(deal, business));
               }

               RevaluateExpiredDeals();

               if (App.ViewModel.SelectedDeal == null && 
                   App.LocationService.CurrentLongitude != null && 
                   App.LocationService.CurrentLatitude != null)
               {
                   ChiantiServiceReference5.Deal closestDeal = GetClosestDeal(App.LocationService.CurrentLatitude, App.LocationService.CurrentLongitude);
                   if (closestDeal != null)
                   {
                       App.ViewModel.SelectDeal(closestDeal.ID);
                   }

                   this.OnViewModelUpdate();
               }
           });
           
            
        }

        private void GrabLoadLock()
        {
           
            while (isBusinessLoading == true || isDealBidsLoading == true || isDealLoading == true)
            {
                monitor.WaitOne();
            }
            
        }

  
        public ChiantiServiceReference5.Business GetBusinessByDeal(long dealID)
        {
            return GetBusiness(GetDeal(dealID).BusinessID); 
        }

        public ChiantiServiceReference5.Business GetBusiness(long businessID)
        {
            ChiantiServiceReference5.Business retVal = null;

            var result = from b in Businesses
                         where b.ID == businessID
                         select b;

            if (result == null || result.Count() == 0)
            {
                throw new Exception(string.Format("GetBusiness: Business with ID: {0} doesn't exist in View model", businessID));
            }
            else
            {
                retVal = result.First<ChiantiServiceReference5.Business>();
            }

            return retVal;
        }

        public ChiantiServiceReference5.Deal GetDeal(long dealID)
        {
            ChiantiServiceReference5.Deal retVal = null;

            var result = from d in Deals
                         where d.ID == dealID
                         select d;

            if (result != null && result.Count() > 0)
            {
                retVal = result.First<ChiantiServiceReference5.Deal>();
               
            }
            else
            {
                throw new Exception(string.Format("GetDeal: Deal with id: {0} does not exist in application view model", dealID));
            }

            return retVal;
        }

        public ChiantiServiceReference5.DealBid GetDealBid(long dealID)
        {
            ChiantiServiceReference5.DealBid retVal = null;
            
            var result = from d in DealBids
                         where d.DealID == dealID
                         select d;

            if (result != null && result.Count() > 0)
            {
                retVal = result.First<ChiantiServiceReference5.DealBid>();

            }
            else
            {
                throw new Exception(string.Format("GetDealBid: Deal with id: {0} does not exist in application view model", dealID));
            }

            return retVal; ;
        }

        private ChiantiServiceReference5.DealSummary GetDealSummary(long dealID)
        {
            ChiantiServiceReference5.DealSummary retVal = null;

            var result = from d in DealSummaries
                         where d.DealID == dealID
                         select d;

            if (result != null && result.Count() > 0)
            {
                retVal = result.First<ChiantiServiceReference5.DealSummary>();

            }
            else
            {
                throw new Exception(string.Format("GetDealSummary: DealSummary with id: {0} does not exist in application view model", dealID));
            }

            return retVal;
        }


        public bool RefreshAllData
        {
            get
            {
                return refreshAllDataOnUpdate;
            }
            set
            {
                refreshAllDataOnUpdate = value;
            }
        }

        public bool IsDataLoaded
        {
            get;
            private set;
        }

        private ChiantiServiceReference5.DealManagementServiceClient Client
        {
            get
            {
                if (_client == null)
                {
                    _client = new ChiantiServiceReference5.DealManagementServiceClient();
                    _client.GetBusinessCompleted += new EventHandler<ChiantiServiceReference5.GetBusinessCompletedEventArgs>(_client_GetBusinessCompleted);
                    _client.GetBusinessesCompleted += new EventHandler<ChiantiServiceReference5.GetBusinessesCompletedEventArgs>(_client_GetBusinessesCompleted);
                    _client.FindActiveDealsCompleted += new EventHandler<ChiantiServiceReference5.FindActiveDealsCompletedEventArgs>(client_FindActiveDealsCompleted);
                    _client.GetDealBidsCompleted += new EventHandler<ChiantiServiceReference5.GetDealBidsCompletedEventArgs>(_client_GetDealBidsCompleted);
                }
                return _client;
            }
        }

        private ChiantiServiceReference5.DealManagementServiceClient CreateNewClient()
        {
            ChiantiServiceReference5.DealManagementServiceClient retVal = new ChiantiServiceReference5.DealManagementServiceClient();
            retVal.GetBusinessCompleted += new EventHandler<ChiantiServiceReference5.GetBusinessCompletedEventArgs>(_client_GetBusinessCompleted);
            retVal.GetBusinessesCompleted += new EventHandler<ChiantiServiceReference5.GetBusinessesCompletedEventArgs>(_client_GetBusinessesCompleted);
            retVal.FindActiveDealsCompleted += new EventHandler<ChiantiServiceReference5.FindActiveDealsCompletedEventArgs>(client_FindActiveDealsCompleted);
            retVal.GetDealBidsCompleted += new EventHandler<ChiantiServiceReference5.GetDealBidsCompletedEventArgs>(_client_GetDealBidsCompleted);
            return retVal;
        }

        /// <summary>
        /// Returns all deals within the local cache which are located within a specific radius of a specified point
        /// </summary>
        /// <param name="latitude"></param>
        /// <param name="longitude"></param>
        /// <param name="radius"></param>
        /// <returns></returns>
        public List<ChiantiServiceReference5.Deal> GetDeals(ChiantiServiceReference5.Position latitude, ChiantiServiceReference5.Position longitude, int radius)
        {
            List<ChiantiServiceReference5.Deal> retVal = new List<ChiantiServiceReference5.Deal>();
            foreach (ChiantiServiceReference5.Deal deal in this.Deals)
            {
                if (!deal.IsExpired())
                {
                    ChiantiServiceReference5.DealBid bid = GetDealBid(deal.ID);
                    ChiantiServiceReference5.Business business = GetBusiness(deal.BusinessID);
                    double distance = App.LocationService.GetDistanceInMeters(longitude, latitude, new ChiantiServiceReference5.Position(business.Longitude), new ChiantiServiceReference5.Position(business.Latitude));
                    if (distance <= radius)
                    {
                        retVal.Add(deal);
                    }
                }
            }
            return retVal;
        }

        /// <summary>
        /// Creates and adds a few ItemViewModel objects into the Items collection.
        /// </summary>
        public void LoadData(ChiantiServiceReference5.Position latitude, ChiantiServiceReference5.Position longitude, int radius)
        {
             //   GrabLoadLock();
                //loadSemaphore.Grab();
                  //Client.FindActiveDealsAsync(longitude,latitude,radius,Constants.MAX_RESULTS_TO_DOWNLOAD,null);
               // this.isDealLoading = true;
               // this.IsDataLoaded = true;
                DownloadDeals(longitude, latitude, radius, Constants.MAX_RESULTS_TO_DOWNLOAD);
        }

        void DownloadDeals(ChiantiServiceReference5.Position longitude, 
                            ChiantiServiceReference5.Position latitude,
                            int radius,
                            int maxResults)
        {

            isDealLoading = true;
            ThreadPool.QueueUserWorkItem(delegate
            {
                ChiantiServiceReference5.DealManagementServiceClient newClient = CreateNewClient();
                ChiantiServiceReference5.IDealManagementService dealService = (ChiantiServiceReference5.IDealManagementService)newClient;
                var asyncResult = dealService.BeginFindActiveDeals(longitude, latitude, radius, maxResults, null, null);
                ChiantiServiceReference5.Deal[] deals = /*new ChiantiServiceReference5.Deal[0];*/ dealService.EndFindActiveDeals(asyncResult);
                FindActiveDealsCompleted(deals.ToList<ChiantiServiceReference5.Deal>());
                isDealLoading = false;
                newClient.CloseAsync();
                monitor.Set();
                OnDealDataDownloadComplete();
            });
            
        }


        void client_FindActiveDealsCompleted(object sender, ChiantiServiceReference5.FindActiveDealsCompletedEventArgs e)
        {
            FindActiveDealsCompleted(e.Result.ToList<ChiantiServiceReference5.Deal>());
        }

        private void FindActiveDealsCompleted(List<ChiantiServiceReference5.Deal> deals)
        {
            List<long> businessIDsToRetrieve = new List<long>();
           // businessIDsToRetrieve.Add(1195);
            List<long> dealBidsToRetrieve = new List<long>();
          //  dealBidsToRetrieve.Add(1386);
            foreach (ChiantiServiceReference5.Deal deal in deals)
            {

                ChiantiServiceReference5.Business currentBusiness = null;
                DateTime currentDate = DateTime.Now;
                currentDate = currentDate.AddSeconds(30);
                deal.ExpiryDate = currentDate;

                if (!DoesCacheContainDeal(deal.ID))
                {
                    this.Deals.Add(deal);

                }
                else
                {
                    ChiantiServiceReference5.Deal currentDeal = GetDeal(deal.ID);
                    this.Deals.Remove(currentDeal);
                    this.Deals.Add(deal);

                }

                if (RefreshAllData || !DoesCacheContainBusiness(deal.BusinessID))
                {
                    businessIDsToRetrieve.Add(deal.BusinessID);
                }
                else
                {
                    currentBusiness = GetBusiness(deal.BusinessID);
                }

                if (RefreshAllData || !DoesCacheContainDealBid(deal.ID))
                {
                    dealBidsToRetrieve.Add(deal.ID);
                }


                if (currentBusiness != null)
                {
                    RecalculateDistance(deal, currentBusiness);
                }

            }
            this.isDealLoading = false;
            //At this point we now need to load all the Businesses that arent present in the cache
            DownloadBusinesses(businessIDsToRetrieve);
            DownloadDealBids(dealBidsToRetrieve);
           

        }

        private void DownloadDealBids(List<long> dealBidsToRetrieve)
        {
            isDealBidsLoading = true;
            ThreadPool.QueueUserWorkItem(delegate
            {
                ChiantiServiceReference5.DealManagementServiceClient newClient = CreateNewClient();
                ChiantiServiceReference5.IDealManagementService dealService = (ChiantiServiceReference5.IDealManagementService)newClient;
                var asyncResult = dealService.BeginGetDealBids(dealBidsToRetrieve.ToArray<long>(), null, null);
                ChiantiServiceReference5.DealBid[] dealBids = dealService.EndGetDealBids(asyncResult);
                GetDealBidsCompleted(dealBids.ToList<ChiantiServiceReference5.DealBid>());
                isDealBidsLoading = false;
                newClient.CloseAsync();
                monitor.Set();
                
            });

        }

        private bool DoesCacheContainDealBid(long dealID)
        {
            var result = from d in DealBids
                         where d.DealID == dealID
                         select d;

            if (result == null || result.Count() == 0)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        private  void RecalculateDistance(ChiantiServiceReference5.Deal deal, ChiantiServiceReference5.Business currentBusiness)
        {
            //need to append additional information on the Deal to indicate distance from the current user's position
            ChiantiServiceReference5.Position currentLatitude = App.LocationService.GetCurrentUserLatitude();
            ChiantiServiceReference5.Position currentLongitude = App.LocationService.GetCurrentUserLongitude();
            ChiantiServiceReference5.Position businessLatitude = new ChiantiServiceReference5.Position(currentBusiness.Latitude);
            ChiantiServiceReference5.Position businessLongitude = new ChiantiServiceReference5.Position(currentBusiness.Longitude);
            double distanceToDeal = App.LocationService.GetDistanceInMeters(currentLongitude, currentLatitude, businessLongitude, businessLatitude);
            deal.DistanceToDealInMeters = distanceToDeal;
        }


        void _client_GetBusinessCompleted(object sender, ChiantiServiceReference5.GetBusinessCompletedEventArgs e)
        {
            isBusinessLoading = false;
        }


        void _client_GetBusinessesCompleted(object sender, ChiantiServiceReference5.GetBusinessesCompletedEventArgs e)
        {
            GetBusinessesCompleted(e.Result.ToList<ChiantiServiceReference5.Business>());
            this.isBusinessLoading = false;
           
        }

        private void GetBusinessesCompleted(List<ChiantiServiceReference5.Business> businesses)
        {
            foreach (ChiantiServiceReference5.Business business in businesses)
            {
                if (!DoesCacheContainBusiness(business.ID))
                {
                    Businesses.Add(business);
                }
                else
                {
                    ChiantiServiceReference5.Business currentDeal = GetBusiness(business.ID);
                    Businesses.Remove(currentDeal);
                    Businesses.Add(business);
                }

                //Get all deals which are owned by this business and updare the user's position
                var ownedDeals = from d in Deals
                                 where d.BusinessID == business.ID
                                 select d;

                foreach (ChiantiServiceReference5.Deal ownedDeal in ownedDeals)
                {
                    RecalculateDistance(ownedDeal, business);
                }
            }
        }

        void _client_GetDealBidsCompleted(object sender, ChiantiServiceReference5.GetDealBidsCompletedEventArgs e)
        {
            GetDealBidsCompleted(e.Result.ToList<ChiantiServiceReference5.DealBid>());
        }

        private void GetDealBidsCompleted(List<ChiantiServiceReference5.DealBid> dealBids)
        {
            foreach (ChiantiServiceReference5.DealBid bid in dealBids)
            {
                if (!DoesCacheContainDealBid(bid.DealID))
                {
                    DealBids.Add(bid);
                }
                else
                {
                    ChiantiServiceReference5.DealBid currentBid = GetDealBid(bid.DealID);
                    DealBids.Remove(currentBid);
                    DealBids.Add(bid);
                }
            }
            //loadSemaphore.Release();
            this.isDealBidsLoading = false;
           
        }


        void DownloadBusinesses(List<long> businessesToDownload)
        {
            isBusinessLoading = true;
            ThreadPool.QueueUserWorkItem(delegate
            {

                ChiantiServiceReference5.DealManagementServiceClient newClient = CreateNewClient();
                ChiantiServiceReference5.IDealManagementService dealService = (ChiantiServiceReference5.IDealManagementService)newClient;
               
               
                var asyncResult = dealService.BeginGetBusinesses(businessesToDownload.ToArray<long>(), null, null);
                
                try
                {
                    ChiantiServiceReference5.Business[] businesses = dealService.EndGetBusinesses(asyncResult);
                    GetBusinessesCompleted(businesses.ToList<ChiantiServiceReference5.Business>());
                }
                catch (Exception ex)
                {
                
                }
               
                isBusinessLoading = false;
                newClient.CloseAsync();
                monitor.Set();
                
            });
            
          
                 //   isBusinessLoading = true;
                    //Client.GetBusinessAsync(businessesToDownload[0]);
                    //Client.GetBusinessesAsync(businessesToDownload.ToArray<long>());
                
        }

        bool DoesCacheContainDeal(long dealID)
        {
            var result = from d in Deals
                         where d.ID == dealID
                         select d;

            if (result == null || result.Count() == 0)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        bool DoesCacheContainBusiness(long businessID)
        {
            
            var result = from b in Businesses
                         where b.ID == businessID
                         select b;

            if (result == null || result.Count() == 0)
            {
                return false;
            }
            else
            {
                return true;
            }

        }
        

        void client_GetDealCompleted(object sender, ChiantiServiceReference5.GetDealCompletedEventArgs e)
        {
           
        }

        

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged(String propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (null != handler)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Iterates through all deal summaries and updates the expiry string
        /// </summary>
        internal void RevaluateDealSummaries()
        {
            foreach (ChiantiServiceReference5.DealSummary dealSummary in DealSummaries)
            {
                dealSummary.ExpiresIn = Utilities.CreateExpiresInString(dealSummary.ExpiryDate - DateTime.Now);
            }
        }
        /// <summary>
        /// Iterates through all of the deals removing any which have expired
        /// </summary>
        internal void RevaluateExpiredDeals()
        {
            //GrabLoadLock();
            //loadSemaphore.Grab();
            DateTime currentDateTime = DateTime.Now;
            var expiredDeals = from d in Deals
                                  where d.ExpiryDate < currentDateTime
                                  select d;

            if (expiredDeals != null && expiredDeals.Count() > 0)
            {
                List<ChiantiServiceReference5.Deal> dealsToRemove = expiredDeals.ToList<ChiantiServiceReference5.Deal>();
                foreach (ChiantiServiceReference5.Deal expiredDeal in dealsToRemove)
                {
                    ChiantiServiceReference5.DealBid dealBid = GetDealBid(expiredDeal.ID);
                    ChiantiServiceReference5.DealSummary dealSummary = GetDealSummary(expiredDeal.ID);
                    DealBids.Remove(dealBid);
                    DealSummaries.Remove(dealSummary);
                    OnDealExpired(expiredDeal);               
                }

                
                foreach (ChiantiServiceReference5.Deal dealToRemove in dealsToRemove)
                {
                    Deals.Remove(dealToRemove);
                }
            }


           // loadSemaphore.Release();
        }


        internal void RevaluateActiveDeal()
        {
            GrabLoadLock();
            //loadSemaphore.Grab();
            //foreach active deal in the cache
            //if the current user is within its radius
            //mark it as the active

            foreach (ChiantiServiceReference5.Deal deal in Deals)
            {
                ChiantiServiceReference5.Business business = GetBusiness(deal.BusinessID);
                ChiantiServiceReference5.DealBid dealBid = GetDealBid(deal.ID);
                ChiantiServiceReference5.Position currentLatitude = App.LocationService.GetCurrentUserLatitude();
                ChiantiServiceReference5.Position currentLongitude = App.LocationService.GetCurrentUserLongitude();
                double distanceInMeters = App.LocationService.GetDistanceInMeters(new ChiantiServiceReference5.Position(business.Longitude), new ChiantiServiceReference5.Position(business.Latitude), currentLongitude, currentLatitude);
               

                if (distanceInMeters <= dealBid.Radius)
                {
                    ChiantiServiceReference5.Deal oldDeal = this.SelectedDeal;
                    

                    //this is the active deal
                    this.SelectedBusiness = business;
                    this.SelectedDeal = deal;
                    this.SelectedActiveDealBid = dealBid;

                    //need to notify that the active deal has changed
                    if (oldDeal == null || oldDeal != this.SelectedDeal)
                    {
                        OnSelectedDealChanged();
                    }
                    
                    
                    break;
                }
            }
           //loadSemaphore.Release();
        }

        internal void SelectDeal(long dealID)
        {
            if (this.SelectedDeal == null || this.SelectedDeal.ID != dealID)
            {
                ChiantiServiceReference5.Deal newDeal = GetDeal(dealID);
                ChiantiServiceReference5.DealBid newDealBid = GetDealBid(dealID);
                ChiantiServiceReference5.Business newBusiness = GetBusinessByDeal(dealID);
                this.SelectedDeal = newDeal;
                this.SelectedActiveDealBid = newDealBid;
                this.SelectedBusiness = newBusiness;
                OnSelectedDealChanged();
            }


            
        }

        internal ChiantiServiceReference5.Deal GetClosestDeal(ChiantiServiceReference5.Position latitude, ChiantiServiceReference5.Position longitude)
        {
            GrabLoadLock();
            //loadSemaphore.Grab();
            ChiantiServiceReference5.Deal retVal = null;
            double closestDistance = Double.MaxValue;

            foreach (ChiantiServiceReference5.Deal deal in Deals)
            {
                ChiantiServiceReference5.Business business = GetBusiness(deal.BusinessID);
                double distance = App.LocationService.GetDistanceInMeters(new ChiantiServiceReference5.Position(business.Longitude),
                                                                           new ChiantiServiceReference5.Position(business.Latitude),
                                                                           longitude,
                                                                           latitude);
                if (distance < closestDistance)
                {
                    retVal = deal;
                    closestDistance = distance;
                }

            }
           // loadSemaphore.Release();
            return retVal;
        }
    }
}